using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using MIMPGameLibrary;

namespace MIMP
{
    /// <summary>
    /// A class implementing IGameStateService is responsible for holding and updating a GameState object. 
    /// It should register handlers for user input events fired by IControlPanelService providers and 
    /// IGameplayScreenService providers.
    /// </summary>
    public interface IGameStateService
    {
        /// <summary>
        /// Gets a reference to the GameState object maintained by this service provider.
        /// </summary>
        GameState GameState { get; }
        /// <summary>
        /// Gets a status message suitable for display to the user which describes the most recent update made
        /// to the GameState.
        /// </summary>
        String StatusMsg { get; }
        /// <summary>
        /// Gets a reference to the Monster the user is currently viewing the stats of.
        /// </summary>
        Monster ViewingMonster { get; }
        /// <summary>
        /// Gets a reference to the Monster that the user may add during the setup phase.
        /// </summary>
        Monster MonsterToAdd { get; }
        /// <summary>
        /// Gets a reference to the UIButton that is currently active.
        /// </summary>
        UIButtons ActiveButton { get; }
        /// <summary>
        /// Fired when the user wants to load a saved game
        /// </summary>
        event EventHandler LoadGame;
        /// <summary>
        /// Fired when a new Monster is added to the game
        /// </summary>
        event EventHandler MonsterAdded;
        /// <summary>
        /// Fired when the ControlPanel should display a different set of visible buttons.
        /// </summary>
        event EventHandler ChangeActiveButtons;
        /// <summary>
        /// Set this to true to ignore user input on the main game components. 
        /// Useful when a popup menu is open that should be the only thing users can click on.
        /// </summary>
        bool IgnoreInput { get; set; }
        /// <summary>
        /// A method that other classes can use to request that the GameStateComponent raise a LoadGame event.
        /// </summary>
        void RaiseLoadGameEvent(GameState gameState);
        /// <summary>
        /// A method that other classes can use to save the current GameState.
        /// </summary>
        /// <param name="filename">The name of the file to save the GameState in, 
        /// omitting the directory path and extension.</param>
        void SaveGame(String filename);
    }

    /// <summary>
    /// An extension of EventArgs intended for use with GameStateService's LoadGame 
    /// event. Specifies the GameState to load.
    /// </summary>
    public class LoadGameArgs : EventArgs
    {
        public LoadGameArgs(GameState state)
        {
            this.stateToLoad = state;
        }
        private GameState stateToLoad;
        /// <summary>
        /// The GameState to load.
        /// </summary>
        public GameState StateToLoad
        {
            get { return stateToLoad; }
        }
    }
    /// <summary>
    /// The EventArgs for GameStateService's ChangeActiveButtons event. Specifies a list of UIButtons that should
    /// be active (i.e. visible and clickable).
    /// </summary>
    public class ChangeActiveButtonsArgs : EventArgs
    {
        public ChangeActiveButtonsArgs(List<UIButtons> active)
        {
            this.activeBtns = active;
        }

        private List<UIButtons> activeBtns;
        /// <summary>
        /// All buttons with ButtonID equal to one of the members of this list should be visible and clickable.
        /// </summary>
        public List<UIButtons> ActiveButtons
        {
            get { return activeBtns; }
        }
    }

    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class GameStateComponent : Microsoft.Xna.Framework.GameComponent, IGameStateService
    {
        public event EventHandler LoadGame;
        public event EventHandler MonsterAdded;
        public event EventHandler ChangeActiveButtons;

        private GameState gameState;
        public GameState GameState
        {
            get { return gameState; }
        }

        private Monster viewingMonster;
        public Monster ViewingMonster
        {
            get { return viewingMonster; }
        }
        private List<Monster> toAdd;
        private int toAddIndex;

        public Monster MonsterToAdd
        {
            get { return toAdd[toAddIndex]; }
        }
        private string status;
        public String StatusMsg
        {
            get { return status; }
        }
        private UIButtons activeButton;
        public UIButtons ActiveButton
        {
            get { return activeButton; }
        }
        private bool ignoreInput;
        public bool IgnoreInput
        {
            get { return ignoreInput; }
            set { ignoreInput = value; }
        }
        MIMPGame game;

        string monsterName;
        List<Monster> defenders;

        public GameStateComponent(MIMPGame game, GameState gameState)
            : base((Game)game)
        {
            this.game = game;
            this.gameState = gameState;
            this.activeButton = UIButtons.None;
            this.status = String.Empty;
            this.ignoreInput = false;
            this.toAdd = Monster.CreateMonstersFromXMLs(MIMPGame.MONSTER_XML_DIR);
            toAddIndex = 0;
            defenders = new List<Monster>();
        }
        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            IGameplayScreenService gsService = (IGameplayScreenService)game.Services.GetService(typeof(IGameplayScreenService));
            gsService.HexLeftClicked += new EventHandler(HexLeftClicked);
            gsService.HexRightClicked += new EventHandler(HexRightClicked);
            gsService.UIButtonLeftClicked += new EventHandler(UIButtonLeftClicked);

            base.Initialize();
        }

        /// <summary>
        /// Handles events fired by GameplayScreen when a hex is left-clicked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void HexLeftClicked(object sender, EventArgs e)
        {
            if (ignoreInput) { return; }
            try
            {
                HexClickEventArgs args = (HexClickEventArgs)e;
                Hex target = args.ClickedHex;
                switch (activeButton)
                {
                    case UIButtons.None:
                        if (target.IsMonsterOn)
                        {
                            //a ctrl-click means "add this monster to or remove it from the selected list"
                            //a regular click means "select just this monster"
                            if (args.IsCtrlDown && gameState.SelectedMonsters.Contains(target.MonsterOn))
                            {
                                gameState.UnselectMonster(target.MonsterOn);
                                status = "Unselected " + target.MonsterOn.Name;
                            }
                            else if (args.IsCtrlDown)
                            {
                                viewingMonster = target.MonsterOn;
                                gameState.SelectMonster(target.MonsterOn);
                                status = "Selected " + target.MonsterOn.Name;
                            }
                            else
                            {
                                gameState.UnselectAllMonsters();
                                viewingMonster = target.MonsterOn;
                                gameState.SelectMonster(target.MonsterOn);
                                status = "Selected " + target.MonsterOn.Name;
                            }
                        }
                        else
                        {
                            gameState.UnselectAllMonsters();
                            viewingMonster = null;
                        }
                        break;
                    case UIButtons.Move:
                        gameState.Move(target);
                        status = "Moved " + target.MonsterOn.Name + " to " + target;
                        break;
                    case UIButtons.Attack:
                        try
                        {
                            gameState.TryAttack(target.MonsterOn);
                            gameState.SelectDefender(target.MonsterOn);
                            activeButton = UIButtons.SelectDefenders;
                            if (ChangeActiveButtons != null)
                            {
                                List<UIButtons> active = new List<UIButtons>();
                                active.Add(UIButtons.SelectDefenders);
                                ChangeActiveButtons(this, new ChangeActiveButtonsArgs(active));
                            }
                            MessageBoxScreen defenseMessageScreen = new MessageBoxScreen(target.MonsterOn.Controller.Name +
                                ", your monster '" + target.MonsterOn.Name + "' is being attacked.\nYou may select additional defenders.", false);
                            game.ScreenManager.AddScreen(defenseMessageScreen);
                        }
                        catch (IllegalActionException iae)
                        {
                            status = "Cannot attack: " + iae.Message;
                            activeButton = UIButtons.None;
                        }
                        break;
                    case UIButtons.SelectDefenders:
                        if (target.IsMonsterOn)
                        {
                            //a ctrl-click means "remove it from the defenders list"
                            //a regular click means "defend with this monster"
                            if (args.IsCtrlDown)
                            {
                                gameState.UnselectDefender(target.MonsterOn);
                                status = "Not defending with " + target.MonsterOn.Name;
                            }
                            else 
                            {
                                gameState.SelectDefender(target.MonsterOn);
                                status = "Defending with " + target.MonsterOn.Name;
                            }
                        }
                        break;
                    case UIButtons.Add:
                        try
                        {
                            Monster newMonster = MonsterToAdd.ShallowCopy();
                            newMonster.Name = monsterName;
                            gameState.DeployMonster(newMonster, args.ClickedHex);
                            activeButton = UIButtons.None;
                            if (MonsterAdded != null) { MonsterAdded(this, new EventArgs()); }
                            status = "Deployed " + newMonster.Name + " to hex " + newMonster.Location + ".";
                        }
                        catch (IllegalDeploymentException ide)
                        {
                            status = "Cannot deploy: " + ide.Message;
                            activeButton = UIButtons.None;
                        }
                        break;
                }
            }
            catch (NullReferenceException nre)
            {
                ExceptionPolicy.HandleException(nre, "MIMP Exception Policy");
            }
            catch (IllegalSelectionException)
            {
                //don't do anything...just let game continue
            }
            catch (IllegalMoveException ime)
            {
                status = "Can't move there: " + ime.Message;
            }
            catch (IllegalActionException iae)
            {
                status = "Can't take that action: " + iae.Message;
            }
        }

        void HexRightClicked(object sender, EventArgs e)
        {
            HexClickEventArgs args = (HexClickEventArgs)e;
            switch (activeButton)
            {
                case UIButtons.Add:
                    activeButton = UIButtons.None;
                    status = "Canceled monster addition.";
                    break;
                default:
                    gameState.UnselectAllMonsters();
                    activeButton = UIButtons.None;
                    break;
            }
        }

        void UIButtonLeftClicked(object sender, EventArgs e)
        {
            if (ignoreInput) { return; }
            try
            {
                UIButtonClickEventArgs args = (UIButtonClickEventArgs)e;
                switch (args.ClickedButton)
                {
                    case UIButtons.Move:
                        if (gameState.IsSelected)
                        {
                            activeButton = UIButtons.Move;
                            status = "Click the hex to move to.";
                        }
                        else
                        {
                            status = "Select a monster first.";
                        }
                        break;
                    case UIButtons.Attack:
                        if (gameState.IsSelected)
                        {
                            status = "Click the hex with your attack target on it.";
                            activeButton = UIButtons.Attack;
                        }
                        else
                        {
                            status = "Select a monster first.";
                        }
                        break;
                    case UIButtons.SelectDefenders:
                        if (activeButton == UIButtons.SelectDefenders)
                        {
                            AttackResult res = gameState.BeginAttack();
                            System.Text.StringBuilder resultMessage = new System.Text.StringBuilder("COMBAT RESULTS:\n");
                            if (res.DidAttackersWin)
                            {
                                resultMessage.Append("The attacker(s), controlled by " + gameState.ActivePlayer.Name + ", won the battle!\n\n");
                            }
                            else if (res.TotalDamageDone == 0)
                            {
                                resultMessage.Append("The battle was a tie!\n\n");
                            }
                            else
                            {
                                resultMessage.Append("The defender(s), controlled by " + gameState.DefendingMonsters[0].Controller.Name + ", won the battle!\n\n");
                            }
                            resultMessage.Append("The total damage done was " + res.TotalDamageDone + ":\n");
                            foreach(KeyValuePair<Monster, int> kvp in res.DamageDoneByMonsters)
                            {
                                resultMessage.Append(kvp.Key.Name);
                                resultMessage.Append("(");
                                resultMessage.Append(kvp.Key.Controller.Name);
                                resultMessage.Append(") did ");
                                resultMessage.Append(kvp.Value);
                                resultMessage.Append(" damage.\n");
                            }
                            MessageBoxScreen resultsBox = new MessageBoxScreen(resultMessage.ToString(), false);
                            game.ScreenManager.AddScreen(resultsBox);

                            gameState.CompleteAttack(res.DamageAssignments);
                            activeButton = UIButtons.None;
                        }
                        if (ChangeActiveButtons != null)
                        {
                            List<UIButtons> active = new List<UIButtons>();
                            active.Add(UIButtons.Attack);
                            active.Add(UIButtons.EndTurn);
                            active.Add(UIButtons.Help);
                            active.Add(UIButtons.Menu);
                            active.Add(UIButtons.Move);
                            active.Add(UIButtons.SecondMove);
                            ChangeActiveButtons(this, new ChangeActiveButtonsArgs(active));
                        }
                        break;
                    case UIButtons.EndTurn:
                        activeButton = UIButtons.None;
                        viewingMonster = null;
                        status = "Ended " + gameState.ActivePlayer + "'s turn.";
                        gameState.EndTurn();
                        break;
                    case UIButtons.Next:
                        toAddIndex = toAddIndex == toAdd.Count - 1 ? 0 : toAddIndex + 1;
                        break;
                    case UIButtons.Prev:
                        toAddIndex = toAddIndex == 0 ? toAdd.Count - 1 : toAddIndex - 1;
                        break;
                    case UIButtons.Add:
                        gameState.UnselectAllMonsters();
                        activeButton = UIButtons.Add;
                        KeyboardInputScreen monsterNameScreen = new KeyboardInputScreen("Enter a name for your new monster.");
                        monsterNameScreen.Accepted += AddMonsterHandler;
                        game.ScreenManager.AddScreen(monsterNameScreen);
                        break;
                    case UIButtons.Del:
                        if (gameState.IsSelected)
                        {
                            status = "Removed " + gameState.SelectedMonsters[0].Name;
                            gameState.UndeployMonster(gameState.SelectedMonsters[0]);
                        }
                        else
                        {
                            status = "Select the monster to remove, then click the Remove Monster button.";
                        }
                        gameState.UnselectAllMonsters();
                        viewingMonster = null;
                        break;
                    case UIButtons.Menu:
                        game.ScreenManager.AddScreen(new MainMenuScreen(true));
                        break;
                    case UIButtons.SetupDone:
                        string message;
                        if (gameState.ActivePlayer.DeploymentPoints > 0)
                        {
                            message = "You have " + gameState.ActivePlayer.DeploymentPoints +
                                " deployment points remaining.\nAre you sure you want to end your setup phase?";
                        }
                        else
                        {
                            message = "Are you sure you want to end your setup phase?";
                        }
                        MessageBoxScreen confirmEndMessageBox = new MessageBoxScreen(message);
                        confirmEndMessageBox.Accepted += SetupDone;
                        game.ScreenManager.AddScreen(confirmEndMessageBox);
                        break;
                }
            }
            catch (NullReferenceException nre)
            {
                ExceptionPolicy.HandleException(nre, "MIMP Exception Policy");
            }
        }

        private void AddMonsterHandler(object sender, StringEventArgs e)
        {
            monsterName = e.Message;
            status = "Click the hex to add " + monsterName + " to (or r-click any hex to cancel)";
        }

        private void SetupDone(object sender, EventArgs e)
        {
            status = "Finished setup for " + gameState.ActivePlayer;
            if (gameState.Players.IndexOf(gameState.ActivePlayer) == gameState.Players.Count - 1)
            {
                gameState.EndSetupPhase();
                MessageBoxScreen setupComplete = new MessageBoxScreen("All players are finished setting up.\nThe game will now begin!", false);
                setupComplete.Accepted += new EventHandler<EventArgs>(setupComplete_Accepted);
            }
            else
            {
                gameState.SetupNextPlayer();
            }
        }

        void setupComplete_Accepted(object sender, EventArgs e)
        {
            RaiseLoadGameEvent(gameState);
        }

        public void RaiseLoadGameEvent(GameState gameState)
        {
            if (this.LoadGame != null)
            {
                LoadGame(this, new LoadGameArgs(gameState));
            }
        }

        public void SaveGame(String filename)
        {
            String mapsDir = ".\\Saved Games\\";
            String mapName = mapsDir + filename + ".mimp";
            IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            DirectoryInfo dir = new DirectoryInfo(mapsDir);
            if (!dir.Exists)
            {
                dir.Create();
            }
            Stream fs = new FileStream(mapName, FileMode.Create, FileAccess.Write, FileShare.None);
            formatter.Serialize(fs, gameState);
            fs.Close();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // TODO: Add your update code here
            base.Update(gameTime);
        }
    }
}